/*
  This file is part of bnc.

  bnc is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  bnc is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with bnc.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "./manager.h"
#include <unistd.h>

const char cTemplate[]="bnc-XXXXXX";
const char cTemporaryDirectory[]="/tmp/";
const char cCopy[]=" (copy)";

void managerConstructor (manager* aManager)
{
  /*
    Prepares sensitive variables.
  */
  aManager->pFile=0;
  aManager->pHeader=0;

  aManager->pCompressor=(compressor*)malloc(sizeof(compressor));
  aManager->pCryptor=(cryptor*)malloc(sizeof(cryptor));

  aManager->pTemporaryDirectory=malloc(16);

  memcpy(aManager->pTemporaryDirectory, cTemporaryDirectory, 5);
  memcpy(aManager->pTemporaryDirectory+5, cTemplate, 11);
}

char* managerPreCompress (manager* aManager, char* aFileName)
{
  /*
    Opens input file, drops the path, creates temporary compressed file.
    Reads matrix, creates header of the compressed file, translates the content.
  */

  FILE* lFileI;
  FILE* lFileO;
  unsigned int i, lLength, lFileNameLength;
  char* lFileName;

  lFileI=fopen(aFileName, "rb");

  if (lFileI==0)
  {
    printf(" cannot pre-compress '%s'.\n", aFileName);
    return 0;
  }

  fseek(lFileI, 0, SEEK_END);
  if (ftell(lFileI)==0)  //Do not compress empty files.
  {
    fclose(lFileI);
    return aFileName;
  }
  rewind(lFileI);

  aManager->pBufferI=(char*)malloc(BUFFERSIZESMALL*sizeof(char));
  aManager->pBufferO=(char*)malloc(BUFFERSIZELARGE*sizeof(char));

  lFileNameLength=strlen(aFileName);

  i=lFileNameLength;
  while (i>0 && aFileName[i]!='/') i--;  //Get the position of last stroke in the file name.

  lFileName=(char*)malloc((lFileNameLength-i+17)*sizeof(char));

  memcpy(lFileName, aManager->pTemporaryDirectory, 15);
  lFileName[15]='/';
  memcpy(lFileName+16, aFileName+i, lFileNameLength-i+1);  //Create the temporary file name.

  while ((lFileO=fopen(lFileName, "r"))!=0)  //If there already is a file with the same name then change the name.
  {
    fclose(lFileO);
    lFileName=(char*)realloc(lFileName, (strlen(lFileName)+8)*sizeof(char));
    memcpy(lFileName+strlen(lFileName)*sizeof(char), cCopy, 8);
  }

  lFileO=fopen(lFileName, "wb");  //Open temporary file.

  if (lFileO==0)
  {
    printf(" cannot pre-compress '%s'.\n", aFileName);
    free(aManager->pBufferO);
    free(aManager->pBufferI);
    return 0;
  }

  while ((lLength=fread(aManager->pBufferI, 1, BUFFERSIZESMALL, lFileI))!=0)  //Read the content of the file.
    compressorReadMatrix(aManager->pCompressor, aManager->pBufferI, lLength);

  memset(aManager->pBufferO, 0, 324);  //Initialize the buffer for tree.
  fwrite(aManager->pBufferO, 1, compressorBuildHeader(aManager->pCompressor, aManager->pBufferO), lFileO);  //Build a header and write it into the file.

  rewind(lFileI);

  while ((lLength=fread(aManager->pBufferI, 1, BUFFERSIZESMALL, lFileI))!=0)  //Read the file and compress it.
  {
    memset(aManager->pBufferO, 0, BUFFERSIZELARGE);

    aManager->pBufferO[0]=compressorGetLastByte(aManager->pCompressor);

    fwrite(aManager->pBufferO, 1, compressorCompress(aManager->pCompressor, aManager->pBufferI, lLength, aManager->pBufferO), lFileO);
  }

  aManager->pBufferO[0]=compressorGetLastByte(aManager->pCompressor);

  fwrite(aManager->pBufferO, 1, 1, lFileO);

  fclose(lFileO);
  fclose(lFileI);

  free(aManager->pBufferO);
  free(aManager->pBufferI);

  return lFileName;
}

char managerCreateBnc (manager* aManager, char* aBnc)
{
  /*
    Creates bnc file to be written into.
  */
  aManager->pFile=fopen(aBnc, "wb");

  if (aManager->pFile==0)
  {
    printf("\nbnc: cannot create bnc file.\n");
    return 0;
  }

  return 1;
}

char managerBuildHeader (manager* aManager, unsigned int aCount, char** aInput)
{
  /*
    If necessary, precompiles  and/or encrypts the files.
    Creates header.
    Writes the header into the bnc.
  */
  FILE* lFile;
  unsigned int i, j, lLength;
  char lCompression=1;
  char lEncryption=0;
  char* lPassword=0;

  mkdtemp(aManager->pTemporaryDirectory);

  printf("compressing:");
  fflush(stdout);

  lLength=0;

  for (i=0; i<aCount; i++)  //Compress all files that are supposed to be.
  {
    if (aInput[i][0]!='-' || (aInput[i][1]!='c' && aInput[i][1]!='e' && aInput[i][1]!='n') || aInput[i][2]!=0) lLength++;
    if (aInput[i][0]=='-' && aInput[i][1]=='c' && aInput[i][2]==0) lCompression=1;
    else if (aInput[i][0]=='-' && aInput[i][1]=='n' && aInput[i][2]==0) lCompression=0;
    else if (lCompression==1 && (aInput[i][0]!='-' || aInput[i][1]!='e' || aInput[i][2]!=0))
    {
      printf(" %s", aInput[i]);
      fflush(stdout);

      compressorConstructor(aManager->pCompressor);

      if ((aInput[i]=managerPreCompress(aManager, aInput[i]))==0)
      {
        compressorDestructor(aManager->pCompressor);
        return 0;
      }

      compressorDestructor(aManager->pCompressor);
    }
  }

  printf(" done\n");

  aManager->pHeader=(unsigned char*)malloc(8*lLength+6);
  memcpy(aManager->pHeader, &lLength, 2);
  lLength=8*lLength+6;

  lCompression=1;
  lEncryption=0;

  for (i=0, j=0; i<aCount; i++)  //Create header.
  {
    if (aInput[i][0]=='-' &&
        aInput[i][1]=='c' &&
	aInput[i][2]==0)
    {
      lCompression=1;
    }
    else if (aInput[i][0]=='-' &&
             aInput[i][1]=='e' &&
	     aInput[i][2]==0)
    {
      lEncryption=1;
    }
    else if (aInput[i][0]=='-' && aInput[i][1]=='n' && aInput[i][2]==0)
    {
      lCompression=0;
      lEncryption=0;
    }
    else if (aInput[i])
    {
      lFile=fopen(aInput[i], "rb");

      if (lFile==0)
      {
        printf("bnc: cannot open file '%s'.\n", aInput[i]);
      } else {
        unsigned int k, lFileNameLength;

        k=1;
        lFileNameLength=strlen(aInput[i]);

        while (k<=lFileNameLength && aInput[i][lFileNameLength-k]!='/') k++;  //Cut the name, dont save the path.
        k--;

        memcpy(aManager->pHeader+8*j+2, &lLength, 4);
        memcpy(aManager->pHeader+8*j+6, &k, 2);

        if (lCompression==1)
          *(aManager->pHeader+8*j+8)=1;
        else
          *(aManager->pHeader+8*j+8)=0;

        if (lEncryption==1)
          *(aManager->pHeader+8*j+9)=1;
        else
          *(aManager->pHeader+8*j+9)=0;

        fseek(lFile, 0, SEEK_END);
        lLength+=ftell(lFile)+k;
        fclose(lFile);
      }
      j++;
    }
  }

  printf("file(s): %u\nsize: ", j);
  if (lLength>1048576) printf("%u MiB (%u B)\n", lLength/1048576, lLength);
  else if (lLength>1024) printf("%u KiB (%u B)\n", lLength/1024, lLength);
  else printf("%u B\n", lLength);

  memcpy(aManager->pHeader+8*j+2, &lLength, 4);

  fwrite(aManager->pHeader, 1, 8*j+6, aManager->pFile);

  return 1;
}

char managerInsertFiles (manager* aManager, unsigned int aCount, char** aInput)
{
  /*
    Copies filenames and content of the files into the bnc.
    Removes temporary files.
  */
  FILE* lFile;
  unsigned int i, j;
  char lCompression=1;
  char lEncryption=0;
  char* lPassword;
  char lAll=0;

  lPassword=(char*)malloc(256*sizeof(char));
  lPassword[0]=0;

  if (managerBuildHeader(aManager, aCount, aInput)==0) return 0;

  printf("processing:");
  fflush(stdout);

  aManager->pBufferI=(char*)malloc(BUFFERSIZELARGE);

  for (i=0, j=0; i<aCount; i++)
  {
    if (aInput[i][0]=='-' && aInput[i][1]=='c' && aInput[i][2]==0) lCompression=1;
    else if (aInput[i][0]=='-' && aInput[i][1]=='e' && aInput[i][2]==0)
    {
      lEncryption=1;
    }
    else if (aInput[i][0]=='-' && aInput[i][1]=='n' && aInput[i][2]==0)
    {
      lAll=0;
      lCompression=0;
      lEncryption=0;
    }
    else
    {
      lFile=fopen(aInput[i], "rb");

      if (lFile==0)
      {
        printf("\nbnc: cannot open file '%s'.\n", aInput[i]);
        return 0;
      } else {
        long int lLength;
        unsigned int lFileNameLength=0;

        memcpy(&lFileNameLength, aManager->pHeader+8*j+6, 2);

        printf(" %s", aInput[i]+strlen(aInput[i])-lFileNameLength);
        fflush(stdout);

        fwrite(aInput[i]+strlen(aInput[i])-lFileNameLength, 1, lFileNameLength, aManager->pFile);

        /*
          Encrypt if told so.
        */
        if (lEncryption==1)
        {
          if (lPassword[0]!=0 && lAll==0)
          {
            char lAnswer=0;
            printf("\nUse the password entered last? [y/n/A]: ");
            fflush(stdin);
  
            while (lAnswer!='y'&& lAnswer!='Y' &&
                   lAnswer!='n' && lAnswer!='N' &&
                   lAnswer!='A')
              scanf("%c", &lAnswer);
  
            if (lAnswer=='n' || lAnswer=='N') lPassword[0]=0;
            if (lAnswer=='A') lAll=1;
          }
  
          if (lPassword[0]==0 && lAll==0)
          {
            printf("\nPassword: ");
            fflush(stdout);
            while (lPassword[0]==0)
              scanf("%256s", lPassword);
          }

          cryptorConstructor(aManager->pCryptor, lPassword, strlen(lPassword));
        }
        while((lLength=fread(aManager->pBufferI, 1, BUFFERSIZELARGE, lFile))!=0)
        {

          if (lEncryption==1)
          {
            unsigned int i;
            for (i=0; i<lLength; i++) aManager->pBufferI[i]^=cryptorCrypt(aManager->pCryptor);
          }

          fwrite(aManager->pBufferI, 1, lLength, aManager->pFile);
        }

        if (lCompression==1)
        {
          if (ftell(lFile)!=0)  //If the file was empty we didnt compress it so we shouldnt remove the original file.
          {
            fclose(lFile);
            remove(aInput[i]);
          } else {
            fclose(lFile);
          }
        } else {
          fclose(lFile);
        }
      }
      j++;
    }
  }

  free(lPassword);
  free(aManager->pBufferI);

  printf(" done\n");
  fclose(aManager->pFile);

  rmdir(aManager->pTemporaryDirectory);

  return 1;
}

char managerOpenBnc (manager* aManager, char* aInput)
{
  /*
    Opens the bnc, reads its header.
  */
  unsigned int lCount, lLength;
  lCount=0;
  lLength=0;

  aManager->pFile=fopen(aInput, "rb");

  if (aManager->pFile==0)
  {
    printf("\nbnc: cannot open file '%s'.\n", aInput);
    return 0;
  }

  fread(&lCount, 1, 2, aManager->pFile);

  aManager->pHeader=(unsigned char*)malloc(8*lCount+6);

  *(unsigned int*)aManager->pHeader=lCount;

  fread(aManager->pHeader+2, 1, 8*lCount+4, aManager->pFile);
  memcpy(&lLength, aManager->pHeader+8*lCount+2, 4);

  printf("file(s): %u\nsize: ", lCount);
  if (lLength>1048576) printf("%u MiB (%u B)\n", lLength/1048576, lLength);
  else if (lLength>1024) printf("%u KiB (%u B)\n", lLength/1024, lLength);
  else printf("%u B\n", lLength);

  return 1;
}

char managerListFiles (manager* aManager)
{
  /*
    List out files in the bnc.
  */
  unsigned int i, lCount, lFileNameMaxLength;
  FILE* lFile;

  lCount=0;

  memcpy(&lCount, aManager->pHeader, 2);

  lFileNameMaxLength=0;

  for (i=0; i<lCount; i++)  //Get the length of the longest filename.
  {
    unsigned int lFileNameLength=0;
    memcpy(&lFileNameLength, aManager->pHeader+8*i+6, 2);
    if (lFileNameMaxLength<lFileNameLength) lFileNameMaxLength=lFileNameLength;
  }

  for (i=0; i<lCount; i++)  //Go through all the files in the archive.
  {
    char* lFileName;
    unsigned int lFileOffset, lFileNameLength, lFileLength;
    unsigned int j;

    lFileOffset=0;
    lFileNameLength=0;
    lFileLength=0;
    memcpy(&lFileOffset, aManager->pHeader+8*i+2, 4);
    memcpy(&lFileNameLength, aManager->pHeader+8*i+6, 2);
    memcpy(&lFileLength, aManager->pHeader+8*i+10, 4);

    lFileLength-=lFileOffset+lFileNameLength;

    lFileName=(char*)malloc(lFileNameLength*sizeof(char));

    fseek(aManager->pFile, lFileOffset, SEEK_SET);
    fread(lFileName, 1, lFileNameLength*sizeof(char), aManager->pFile);
    lFileName[lFileNameLength]=0;

    printf("%s", lFileName);
    for (j=0; j<lFileNameMaxLength-lFileNameLength; j++) printf(" ");
    fflush(stdout);

    printf("\t%u B", lFileLength);

    if (aManager->pHeader[8*i+8]==1 && aManager->pHeader[8*i+9]!=1)
    {
      unsigned int lFileLengthOrig;

      fseek(aManager->pFile, lFileOffset+lFileNameLength, SEEK_SET);
      fread(&lFileLengthOrig, 1, 4, aManager->pFile);
      if (lFileLength==0) lFileLengthOrig=0;
      printf(", compressed %u B", lFileLengthOrig);
    }

    if (aManager->pHeader[8*i+9]==1) printf(", encrypted");

    printf("\n");
  }

  return 1;
}

char managerExtractFiles (manager* aManager, unsigned int aCount, char** aFiles)
{
  /*
    Extracts files from the bnc.
  */
  unsigned int i, lCount;
  char lPassword[256];
  char lAll=0;
  FILE* lFile;

  lPassword[0]=0;
  lCount=0;

  memcpy(&lCount, aManager->pHeader, 2);

  printf("processing:");
  fflush(stdout);

  for (i=0; i<lCount; i++)  //Go through all the files in the archive.
  {
    char* lFileName;
    unsigned int lFileOffset, lFileNameLength, lFileLength, lLength;
    unsigned int j;
    char lFound;

    lFound=0;

    lFileOffset=0;
    lFileNameLength=0;
    lFileLength=0;

    memcpy(&lFileOffset, aManager->pHeader+8*i+2, 4);
    memcpy(&lFileNameLength, aManager->pHeader+8*i+6, 2);
    memcpy(&lFileLength, aManager->pHeader+8*i+10, 4);

    lFileLength-=lFileOffset+lFileNameLength;

    lFileName=(char*)malloc((lFileNameLength+1)*sizeof(char));

    fseek(aManager->pFile, lFileOffset, SEEK_SET);
    fread(lFileName, 1, lFileNameLength*sizeof(char), aManager->pFile);
    lFileName[lFileNameLength]=0;

    for (j=0; lFound==0 && j<aCount; j++)  //Search through all the files specified by user.
    {
      if (strcmp(aFiles[j], lFileName)==0) lFound=1;
    }

    if (lFound || aCount==0)  //If the list of specified files was empty or the file was on the list then extract it.
    {
      printf(" %s", lFileName);
      fflush(stdout);

      while ((lFile=fopen(lFileName, "r"))!=0)  //If there already is a file with the same name then change the name.
      {
        fclose(lFile);
        lFileName=(char*)realloc(lFileName, (strlen(lFileName)+8)*sizeof(char));
        memcpy(lFileName+strlen(lFileName)*sizeof(char), cCopy, 8);
      }

      lFile=fopen(lFileName, "wb");

      if (aManager->pHeader[8*i+8]==1)  //If it was compressed then decompress it.
      {
        aManager->pBufferI=(char*)malloc(BUFFERSIZESMALL);
        aManager->pBufferO=(char*)malloc(BUFFERSIZELARGE);

        compressorConstructor(aManager->pCompressor);

        lLength=fread(aManager->pBufferI, 1, lFileLength>BUFFERSIZESMALL?BUFFERSIZESMALL:lFileLength, aManager->pFile);

        /*
          Decrypt.
        */
        if (aManager->pHeader[8*i+9]==1)
        {
          unsigned int k;

          if (lPassword[0]!=0 && lAll==0)
          {
            char lAnswer=0;
            printf("\nThe file is encrypted, use the password entered last? [y/n/A]: ");
            fflush(stdin);

            while (lAnswer!='y' && lAnswer!='Y' && lAnswer!='n' && lAnswer!='N' && lAnswer!='A')
              scanf("%c", &lAnswer);

            if (lAnswer=='n' || lAnswer=='N') lPassword[0]=0;
            if (lAnswer=='A') lAll=1;
          }

          if (lPassword[0]==0 && lAll==0)
          {
            printf("\nThe file is encrypted, please type in the password: ");
            fflush(stdout);
            while (lPassword[0]==0)
              scanf("%256s", lPassword);
          }

          cryptorConstructor(aManager->pCryptor, lPassword, strlen(lPassword));

          for (k=0; k<lLength; k++) aManager->pBufferI[k]^=cryptorCrypt(aManager->pCryptor);
        }

        compressorRecoverHeader(aManager->pCompressor, aManager->pBufferI);

        fwrite(aManager->pBufferO, 1 , compressorDecompress(aManager->pCompressor, aManager->pBufferI, lLength, aManager->pBufferO), lFile);

        while ((lLength=fread(aManager->pBufferI, 1, lFileLength>BUFFERSIZESMALL?BUFFERSIZESMALL:lFileLength, aManager->pFile))!=0)
        {
          /*
            Decrypt.
          */
          if (aManager->pHeader[8*i+9]==1)
          {
            unsigned int k;
            for (k=0; k<lLength; k++) aManager->pBufferI[k]^=cryptorCrypt(aManager->pCryptor);
          }

          lFileLength-=lLength;
          fwrite(aManager->pBufferO, 1 , compressorDecompress(aManager->pCompressor, aManager->pBufferI, lLength, aManager->pBufferO), lFile);
        }      

        compressorDestructor(aManager->pCompressor);

        free(aManager->pBufferO);
        free(aManager->pBufferI);
      } else {  //Else transfer it without decompression.
        aManager->pBufferI=(char*)malloc(BUFFERSIZELARGE);

        /*
          Decrypt.
        */
        if (aManager->pHeader[8*i+9]==1)
        {
         if (lPassword[0]!=0 && lAll==0)
         {
            char lAnswer=0;
            printf("\nThe file is encrypted, use the password entered last? [y/n/A]: ");
            fflush(stdin);

            while (lAnswer!='y' && lAnswer!='Y' && lAnswer!='n' && lAnswer!='N' && lAnswer!='A')
              scanf("%c", &lAnswer);

            if (lAnswer=='n' || lAnswer=='N') lPassword[0]=0;
            if (lAnswer=='A') lAll=1;
          }

          if (lPassword[0]==0 && lAll==0)
          {
            printf("\nThe file is encrypted, please type in the password: ");
            fflush(stdout);
            while (lPassword[0]==0)
              scanf("%256s", lPassword);
          }

          cryptorConstructor(aManager->pCryptor, lPassword, strlen(lPassword));
        }


        while ((lLength=fread(aManager->pBufferI, 1, lFileLength>BUFFERSIZELARGE?BUFFERSIZELARGE:lFileLength, aManager->pFile))!=0)
        {
          unsigned int k;


          if (aManager->pHeader[8*i+9]==1) for (k=0; k<lLength; k++) aManager->pBufferI[k]^=cryptorCrypt(aManager->pCryptor);

          lFileLength-=lLength;
          fwrite(aManager->pBufferI, 1, lLength, lFile);
        }

        free(aManager->pBufferI);
      }

      fclose(lFile);
    }
  }

  printf(" done\n");

  return 1;
}

void managerDestructor (manager* aManager)
{
  /*
    Gets rid of manager.
  */

  free(aManager->pHeader);
  free(aManager->pCompressor);
  free(aManager->pCryptor);
  free(aManager->pTemporaryDirectory);
}
